from fun.util import isfloat

def truevalue(origin):
    if isinstance(origin, Reference):
        return truevalue(origin.rvalue)
    return origin

class Reference:
    def __str__(self):
        return str(self.rvalue)

    def __call__(self, args, kwargs):
        return self.rvalue(args, kwargs)

class Identifier(Reference):
    def __init__(self, name, package):
        self.name = name
        self.package = package
        self.__setattr__ = lambda self, name, value: setattr(self.rvalue, name, value)

    def __repr__(self) -> str:
        return self.name

    @property
    def rvalue(self):
        return self.package.find(self.name)

    def set(self, value):
        return self.package.set(self.name, value)

class Node:
    def __call__(self, args, kwargs):
        res = self.execute()
        if hasattr(res, '__macro__'):
            return res(args, kwargs)
        return res(*(truevalue(i.execute()) for i in args), **{k.value.name: truevalue(v.execute()) for k, v in kwargs.items()})

class Value(Node):
    def __init__(self, value):
        self.value = value

    def __str__(self) -> str:
        return repr(self.value)
        
    def execute(self):
        return truevalue(self.value)

class Call(Node):
    current = None

    def __init__(self, caller, package):
        self.caller = caller
        self.package = package
        self.args = []
        self.kwargs = {}
        self.kwrequired = None

    def __str__(self) -> str:
        return f'{self.caller}(' + ' '.join(map(str, self.args)) + ')'

    def no_push(self):
        former = Call.current
        Call.current = self.package
        ret = self.caller.no_push(self.args, self.kwargs)
        Call.current = former
        return ret

    def execute(self):
        former = Call.current
        Call.current = self.package
        ret = self.caller(self.args, self.kwargs)
        Call.current = former
        return ret

    def drop(self):
        if self.kwrequired is not None:
            ret = self.kwargs[self.kwrequired] = Call(self.kwargs[self.kwrequired], self.package)
            self.kwrequired = None
            return ret
        self.args[-1] = Call(self.args[-1], self.package)
        return self.args[-1]

    def dropattr(self):
        if self.kwrequired is not None:
            self.kwargs[self.kwrequired] = Value(Attribute(self.kwargs[self.kwrequired]))
        self.args[-1] = Value(Attribute(self.args[-1]))

    def dropkw(self):
        self.kwrequired = self.args.pop()
        self.kwargs[self.kwrequired] = None

    def append(self, arg):
        if None in self.kwargs.values():
            self.kwargs[self.kwrequired] = arg
        else:
            self.args.append(arg)

class Attribute(Reference):
    def __init__(self, value):
        self.value = value
        self.attr = None

    def __repr__(self) -> str:
        return f'{self.value}.{self.attr}'

    def set(self, value):
        setattr(self.value.execute(), self.attr, value)

    @property
    def rvalue(self):
        return getattr(truevalue(self.value.execute()), self.attr)

def get_node(s: str, package) -> Value | Identifier:
    if s in package.keywords:
        return Value(package.keywords[s])
    if s.startswith('"'):
        return Value(s[1:-1].replace('""', '"'))
    if s.startswith("'"):
        return Value(s[1:-1].replace("''", "'"))
    if s.isnumeric():
        return Value(int(s))
    if isfloat(s):
        return Value(float(s))
    return Value(Identifier(s, package))